package cn;

import android.app.Activity;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.concurrent.Callable;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import cn.util.ManifestParser;
import cn.util.Model;
import cn.util.ScanLoaderUtil;

public class ScanLoaderTask implements Callable<Boolean> {


    private String workDir = null;
    private String buildDirPath = null;
    private File sdkFile = null;
    private String jarlibRules = null;

    private int counter = 0;

    public ScanLoaderTask(String workDir, String buildDirPath, String jarlibRules, File sdkFile) {
        this.sdkFile = sdkFile;
        this.buildDirPath = buildDirPath;
        this.workDir = workDir;
        this.jarlibRules = jarlibRules;
    }

    public Set<String> findBuildDirClassPath(String rootPath ) {

        Set<String> classpaths = new HashSet<>();
        if (rootPath == null || rootPath.length() == 0) {
            return classpaths;
        }
        File f = new File(rootPath);
        if (!f.exists()) {
            return classpaths;
        }
        Stack<String> stack = new Stack<>();
        stack.push(rootPath);

        while (!stack.empty()) {

            String pop = stack.pop();
            if (pop == null || pop.length() == 0) continue;
            File popFile = new File(pop);

            if (popFile.isFile()) {
                String absolutePath = popFile.getAbsolutePath();
                if(!absolutePath.contains("build/intermediates")){
                    continue;
                }
                if (!ScanLoaderUtil.canInclude(absolutePath)) continue;
                classpaths.add(absolutePath);
                continue;
            }

            File[] list = popFile.listFiles();
            if (list == null || list.length == 0) continue;
            for (int i = 0; i < list.length; i++) {
                File currFile = list[i];
                String absolutePath = currFile.getAbsolutePath();
                if(currFile.isFile() && !absolutePath.contains("/build/intermediates")){
                    continue;
                }
                if(shoudExclude(absolutePath)){
                    continue;
                }
                System.out.println("search:"+absolutePath);
                stack.push(absolutePath);
            }
        }

        return classpaths;

    }

    private boolean shoudExclude(String absPath) {
        return  absPath.contains("/.idea")
                ||absPath.contains("/.idea")
                ||absPath.contains("/.DS_Store")
                ||absPath.contains("/assets/")
                ||absPath.contains("/.git/")
                ||absPath.contains("/cmbuild/")
                ||absPath.endsWith(".jpeg")
                ||absPath.endsWith(".jpg")
                ||absPath.endsWith(".txt")
                ||absPath.endsWith(".dat")
                ||absPath.endsWith(".png");
    }

    public Set<String> findJarLibsPath(String rootPath) {

        Set<String> classpaths = new HashSet<>();
        if (rootPath == null || rootPath.length() == 0) {
            return classpaths;
        }
        File f = new File(rootPath);
        if (!f.exists()) {
            return classpaths;
        }
        Stack<String> stack = new Stack<>();
        stack.push(rootPath);

        while (!stack.empty()) {

            String pop = stack.pop();
            if (pop == null || pop.length() == 0) continue;
            File popFile = new File(pop);

            if (popFile.isFile()) {
                String absolutePath = popFile.getAbsolutePath();
                if (!absolutePath.endsWith(".jar")) {
                    continue;
                }
                if(!isRulesAllow(absolutePath)){
                    continue;
                }

                classpaths.add(absolutePath);
                continue;
            }

            File[] list = popFile.listFiles();
            if (list == null || list.length == 0) continue;
            for (int i = 0; i < list.length; i++) {
                String absolutePath = list[i].getAbsolutePath();

                if(!isRulesAllow(absolutePath)){
                    continue;
                }
                stack.push(absolutePath);
            }
        }

        return classpaths;

    }

    private boolean isRulesAllow(String absolutePath) {


        if(absolutePath.contains("/build/")){
            return false;
        }

        String[] rules =  this.jarlibRules.split(";");
        for (String rule: rules) {
            if(rule.contains("*")){
                if(absolutePath.contains(rule)) {
                    return true;
                }
                continue;
            }
            String[] subRules = rule.split("\\*");
            int lastIndex = -1;
            for (String subRule: subRules) {
                int index  = absolutePath.indexOf(subRule);
                if(index==-1 || index<lastIndex) {
                    lastIndex = -1;
                    break;
                }
                lastIndex = index;
            }

            if(lastIndex>=0) {
                return true;
            }
        }

        return false;
    }

    @Override
    public Boolean call()  {

        try {
            Set<String> jarLibsPaths =  findJarLibsPath(this.workDir);
            Set<String> classPathset = findBuildDirClassPath(this.workDir);
            AppClassLoader appClassLoader = new AppClassLoader();

            classPathset.add(this.sdkFile.getAbsolutePath());
            classPathset.addAll(jarLibsPaths);

            for (String path : classPathset) {
                appClassLoader.addClassPath(path);
            }

            appClassLoader.startLoadClasses();

            Set<String> classNames = new HashSet<>();

            for (String path:classPathset ) {
                if(path.endsWith(".class")){
                    String name = getActivity(path);
                    if(name!=null && name.length()>0){
                        classNames.add(name);
                    }
                }else if(path.endsWith(".jar")){
                    if(path.equals(sdkFile.getAbsolutePath())) continue;
                    Collection<String>  classNameSets = getActivitiesIfFound(path);
                    if(classNameSets!=null){
                        classNames.addAll(classNameSets);
                    }
                }
            }

            List<Model> manifestModels = ManifestParser.loadManifest("/Users/tianweitong/GitRespo/CM/cmcn/CleanMaster/build/intermediates/manifests/full/cn/debug/AndroidManifest.xml");
            if(manifestModels!=null){
                List<String> list =  new ArrayList<>();
                for (Model model: manifestModels){
                    list.add(model.name);
                }

                int unrefSize  = 0;
                for (String name:classNames){
                    if(!list.contains(name)){
                        unrefSize++;
                        System.err.println(unrefSize+" unregister: "+name);
                    }
                }

            }

        }catch (Exception e){
            e.printStackTrace();
            return false;
        }

        return true;
    }

    private Collection<String> getActivitiesIfFound(String jarPath) {

        if(jarPath==null) return null;
        JarFile jarFile = null;

        Set<String> classNames = new HashSet<>();

        try {
            jarFile = new JarFile(jarPath);
            Enumeration<JarEntry> entries = jarFile.entries();
            do {
                if(!entries.hasMoreElements())  break;
                JarEntry jarEntry = entries.nextElement();
                String jarName = jarEntry.getName();
                if (jarEntry.isDirectory() || !jarName.endsWith(".class")) {
                   continue;
                }
                // 将文件路径名转换为包名称的形式
                if(jarName.endsWith("module-info.class")){
                    continue;
                }
                String name = getClassNameIfIsActivity(jarName);
                if(name!=null && name.length()>0){
                    classNames.add(name);
                }
            }while (true);

        } catch (IOException e) {
            e.printStackTrace();
        }

        return classNames;


    }


    private String  getActivity(String path) {

        String className = ScanLoaderUtil.getClassName(path);
        if(className==null || className.length()==0) return null;
        return getClassNameIfIsActivity(className);


    }



    private String  getClassNameIfIsActivity(String part) {
        String classFile = part;
        classFile = classFile.substring(0,classFile.length()-6);
        String className = classFile.replaceAll(File.separator,".");

        try {

            if(className.contains("$")) return null;
            if(!className.endsWith("Activity")) return null;
            if(className.startsWith("android.")) return null;


            Class clz = Class.forName(className,false,AppClassLoader.getLoader());
            boolean isActivity = Activity.class.isAssignableFrom(clz);
            if(isActivity){
                counter++;
                System.out.println("Activity "+ counter+" ->: "+className);
                return className;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

}
